package com.warm.common.image;

import lombok.Data;
import sun.font.FontDesignMetrics;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.geom.AffineTransform;
import java.awt.image.*;
import java.io.*;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


/**
 * @Description: 图片处理工具类：文字水印
 * @Author iyb-liangchuang
 * @Date 2020/12/16 10:08
 */
public class ImageUtil {
    /** 字体名称 */
    private String fontName;
    /** 字体样式 */
    private Integer fontStyle;
    /** 透明度：alpha 必须是范围 [0.0, 1.0] 之内（包含边界值）的一个浮点数字 */
    private Float alpha;
    /** 图片模板 */
    private String sourceTemplate;
    /** 目标路径 */
    private String targetPath;

    public ImageUtil(String fontName, Integer fontStyle, Float alpha, String sourceTemplate, String targetPath){
        this.fontName = fontName;
        this.fontStyle = fontStyle;
        this.alpha = alpha;
        this.sourceTemplate = sourceTemplate;
        this.targetPath = targetPath;
    }

    public ImageUtil(String sourceTemplate, String targetPath){
        this.fontName = "KaiTi";
        this.fontStyle = Font.BOLD;
        this.alpha = 1.0f;
        this.sourceTemplate = sourceTemplate;
        this.targetPath = targetPath;
    }

    public ImageUtil(String sourceTemplate){
        this.fontName = "KaiTi";
        this.fontStyle = Font.BOLD;
        this.alpha = 1.0f;
        this.sourceTemplate = sourceTemplate;
    }

    public ImageUtil(){
        this.fontName = "KaiTi";
        this.fontStyle = Font.BOLD;
        this.alpha = 1.0f;
    }

    public static ImageUtil get() {
        return ImageEnum.INSTANCE.getInstance();
    }

    private enum ImageEnum {
        INSTANCE;
        private ImageUtil instance;

        ImageEnum() {
            instance = new ImageUtil();
        }

        public ImageUtil getInstance() {
            return instance;
        }
    }

    @Data
    public static class Draw{
        /** 要填写的文字 */
        private String text;
        /** 是否单行显示文本 */
        private Boolean singleLine;
        /** 绘画 y轴 高度位置 */
        private Integer y;
        /** 绘画 x轴 宽度位置 值为-1的时候默认居中位置 */
        private Integer x;
        /** 绘画字体大小 */
        private Integer size;
        /** 绘画字体颜色 */
        private Color color;
    }

    @Data
    private static class Row{
        private String text;
        private Integer x;
        private Integer y;
    }

    public void pressTextByFile(List<Draw> drawList){
        pressTextByFile(drawList, 100, 100, 5);
    }

    public final void pressTextByFile(List<Draw> drawList, String sourceTemplate){
        this.sourceTemplate = sourceTemplate;
        pressTextByFile(drawList, 100, 100, 5);
    }

    /**
     *
     * @param drawList 要在图片绘画的文字参数配置
     * @param widthUnDrawLeft 图片左侧不绘画的宽度  70
     * @param widthUnDrawRight 图片右侧不绘画的宽度  70
     * @param textHigh 图片文字分行时 间隔高度  5
     */
    public void pressTextByFile(List<Draw> drawList, Integer widthUnDrawLeft, Integer widthUnDrawRight, Integer textHigh){
        try {
            File img = new File(sourceTemplate);
            Image src = ImageIO.read(img);
            BufferedImage image = pressText(drawList, widthUnDrawLeft, widthUnDrawRight, textHigh, src);
            // 输出到文件中
            ImageIO.write(image, "png", new File(targetPath));// 输出到文件流
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public InputStream pressTextByUrl(List<Draw> drawList){
        return pressTextByUrl(drawList, 100, 100, 5);
    }

    public final InputStream pressTextByUrl(List<Draw> drawList, String sourceTemplate){
        this.sourceTemplate = sourceTemplate;
        return pressTextByUrl(drawList, 100, 100, 5);
    }

    public InputStream pressTextByUrl(List<Draw> drawList, Integer widthUnDrawLeft, Integer widthUnDrawRight, Integer textHigh){
        ByteArrayOutputStream os = null;
        InputStream in = null;
        try {
            URL url = new URL(sourceTemplate);
            Image src = ImageIO.read(url);
            BufferedImage image = pressText(drawList, widthUnDrawLeft, widthUnDrawRight, textHigh, src);
            os = new ByteArrayOutputStream();

            ImageIO.write(image, "jpg", os); // 输出到流中
            in = new ByteArrayInputStream(os.toByteArray());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            closeStream(os, in);
        }
        return in;
    }

    /**
     *   给图片添加文字水印
     * @param drawList 添加文字位置集合
     * @param widthUnDrawLeft 左边不填充宽度 70
     * @param widthUnDrawRight 右边不填充宽度 70
     * @param textHigh 文字换行间隔高度 5
     * @param src
     * @return
     */
    private BufferedImage pressText(List<Draw> drawList, Integer widthUnDrawLeft, Integer widthUnDrawRight, Integer textHigh, Image src){
        int picWidth = src.getWidth(null);
        int picHeight = src.getHeight(null);
        BufferedImage image = new BufferedImage(picWidth, picHeight, BufferedImage.TYPE_INT_RGB);
        Graphics2D g = image.createGraphics();
        g.drawImage(src, 0, 0, picWidth, picHeight, null);
        g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
        // 在指定坐标绘制水印文字
        Font font = null;
        for(Draw draw : drawList){
            if(draw.getColor() == null) {
                g.setColor(Color.BLACK);
            } else {
                g.setColor(draw.getColor());
            }

            font = new Font(fontName, fontStyle, draw.getSize());
            g.setFont(font);
            List<Row> rowList = handleText(g, draw, picWidth, widthUnDrawLeft, widthUnDrawRight, textHigh);
            rowList.forEach(row -> {
                g.drawString(row.getText(), row.getX(), row.getY());
            });
        }
        g.dispose();
        return image;
    }

    /** 处理单条文字 需要换行的数据 集合形式保存 */
    private List<Row> handleText(Graphics2D g, Draw draw, int picWidth,
                                                 Integer widthUnDrawLeft, Integer widthUnDrawRight, Integer textHigh){
        String text = draw.getText();
        if(draw.getSingleLine()){
            // 文字单行显示 字体缩小
            resetFont(g, text, picWidth, widthUnDrawLeft, widthUnDrawRight);
        }
        // 文字多行显示
        return getTextRows(text, g.getFont(), picWidth, draw.getX(), draw.getY(), widthUnDrawLeft, widthUnDrawRight, textHigh);
    }

    /** 获取集合 */
    private List<Row> getTextRows(String text, Font font, Integer picWidth, Integer x, Integer y,
                                  Integer widthUnDrawLeft, Integer widthUnDrawRight, Integer textHigh) {
        FontDesignMetrics wMetrics = FontDesignMetrics.getMetrics(font);
        int wordH = wMetrics.getHeight();
        int textWidth = wMetrics.stringWidth(text);
        int wordL = 0;
        int iWidth = 0;
        // 如果 x ！= -1 时 居中显示 实际可填充的宽度为 picWidth - x - widthUnDrawRight
        int paddingL = (x == -1) ? (picWidth - widthUnDrawLeft - widthUnDrawRight) : (picWidth - x - widthUnDrawRight);
        if(textWidth > paddingL){
            // 记录多少个字符长度 == paddingL
            for(int i =0; i < text.length(); i++){
                wordL += wMetrics.charWidth(text.charAt(i));
                if(paddingL <= wordL){
                    iWidth = i;
                    break;
                }
            }
        }
        List<Row> rowList = new ArrayList<>();
        Row row = null;
        int mid = 0;
        if (iWidth > 0){
            // 需要换行
            int size = (int) Math.ceil(text.length()*1.0/iWidth);
            for(int i = 0; i < size; i++){
                row = new Row();
                if((i+1)*iWidth < text.length()){
                    row.setText(text.substring(i*iWidth, (i+1)*iWidth));
                } else {
                    row.setText(text.substring(i*iWidth));
                }
                row.setY(y + textHigh + i*wordH);
                // 设置 x (x==-1 时文字居中, 否则直接设置)
                mid = (x == -1) ? getMidLength(row.getText(), font, picWidth) : x;
                row.setX(mid);
                rowList.add(row);
            }
        } else {
            row = new Row();
            row.setText(text);
            row.setY(y);
            mid = (x == -1) ? getMidLength(row.getText(), font, picWidth) : x;
            row.setX(mid);
            rowList.add(row);
        }
        return rowList;
    }

    /** 单行文字填充 重设字体大小 */
    private void resetFont(Graphics2D g, String text, int picWidth, Integer widthUnDrawLeft, Integer widthUnDrawRight){
        Font font = g.getFont();
        FontDesignMetrics wMetrics = FontDesignMetrics.getMetrics(font);
        int wordL = wMetrics.stringWidth(text);
        // widthUnDrawLeft + widthUnDrawRight 是测算得到的边框宽度
        int paddingL = picWidth - widthUnDrawLeft - widthUnDrawRight;
        if(wordL > paddingL){
            // 递归字体每次缩小 2  直到文字长度小于填充文字的长度
            g.setFont(font.deriveFont(font.getSize() - 2f));
            resetFont(g, text, picWidth, widthUnDrawLeft, widthUnDrawRight);
        }
    }

    /** 计算填充文字的 x 居中位置 */
    private int getMidLength(String text, Font font, int picWidth) {
        FontDesignMetrics wMetrics = FontDesignMetrics.getMetrics(font);
        int wordL = wMetrics.stringWidth(text);
        int mid = (picWidth - wordL) / 2;
        return mid;
    }

    /** 关闭流操作 */
    private void closeStream(ByteArrayOutputStream os, InputStream in){
        if(os != null){
            try {
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(in != null){
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /************************ 缩放图像 ***************************/
    /**
     * 缩放图像（按比例缩放）
     * @param scale 缩放比例
     * @param flag 缩放选择:true 放大; false 缩小;
     */
    public final void scaleByFile(int scale, boolean flag) {
        try {
            BufferedImage src = ImageIO.read(new File(sourceTemplate)); // 读入文件
            int width = src.getWidth(); // 得到源图宽
            int height = src.getHeight(); // 得到源图长
            if (flag) {// 放大
                width = width * scale;
                height = height * scale;
            } else {// 缩小
                width = width / scale;
                height = height / scale;
            }
            Image image = src.getScaledInstance(width, height,
                    Image.SCALE_DEFAULT);
            BufferedImage tag = new BufferedImage(width, height,
                    BufferedImage.TYPE_INT_RGB);
            Graphics g = tag.getGraphics();
            g.drawImage(image, 0, 0, null); // 绘制缩小后的图
            g.dispose();
            ImageIO.write(tag, "jpg", new File(targetPath));// 输出到文件流
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 缩放图像（按高度和宽度缩放）
     * @param height 缩放后的高度
     * @param width 缩放后的宽度
     * @param bb 比例不对时是否需要补白：true为补白; false为不补白;
     */
    public final void scaleByFile2(int height, int width, boolean bb) {
        try {
            double ratio = 0.0; // 缩放比例
            File f = new File(sourceTemplate);
            BufferedImage bi = ImageIO.read(f);
            Image itemp = bi.getScaledInstance(width, height, bi.SCALE_SMOOTH);
            // 计算比例
            if ((bi.getHeight() > height) || (bi.getWidth() > width)) {
                if (bi.getHeight() > bi.getWidth()) {
                    ratio = (new Integer(height)).doubleValue()
                            / bi.getHeight();
                } else {
                    ratio = (new Integer(width)).doubleValue() / bi.getWidth();
                }
                AffineTransformOp op = new AffineTransformOp(AffineTransform
                        .getScaleInstance(ratio, ratio), null);
                itemp = op.filter(bi, null);
            }
            if (bb) {//补白
                BufferedImage image = new BufferedImage(width, height,
                        BufferedImage.TYPE_INT_RGB);
                Graphics2D g = image.createGraphics();
                g.setColor(Color.white);
                g.fillRect(0, 0, width, height);
                if (width == itemp.getWidth(null)){
                    g.drawImage(itemp, 0, (height - itemp.getHeight(null)) / 2,
                            itemp.getWidth(null), itemp.getHeight(null),
                            Color.white, null);
                } else {
                    g.drawImage(itemp, (width - itemp.getWidth(null)) / 2, 0,
                            itemp.getWidth(null), itemp.getHeight(null),
                            Color.white, null);
                }
                g.dispose();
                itemp = image;
            }
            ImageIO.write((BufferedImage) itemp, "JPEG", new File(targetPath));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /************************ 切割图像 ***************************/
    /**
     * 图像切割(按指定起点坐标和宽高切割)
     * @param x 目标切片起点坐标X
     * @param y 目标切片起点坐标Y
     * @param width 目标切片宽度
     * @param height 目标切片高度
     */
    public final void cutByFile(int x, int y, int width, int height) {
        try {
            // 读取源图像
            BufferedImage bi = ImageIO.read(new File(sourceTemplate));
            int srcWidth = bi.getHeight(); // 源图宽度
            int srcHeight = bi.getWidth(); // 源图高度
            if (srcWidth > 0 && srcHeight > 0) {
                Image image = bi.getScaledInstance(srcWidth, srcHeight,
                        Image.SCALE_DEFAULT);
                // 四个参数分别为图像起点坐标和宽高
                // 即: CropImageFilter(int x,int y,int width,int height)
                ImageFilter cropFilter = new CropImageFilter(x, y, width, height);
                Image img = Toolkit.getDefaultToolkit().createImage(
                        new FilteredImageSource(image.getSource(),
                                cropFilter));
                BufferedImage tag = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
                Graphics g = tag.getGraphics();
                g.drawImage(img, 0, 0, width, height, null); // 绘制切割后的图
                g.dispose();
                // 输出为文件
                ImageIO.write(tag, "JPEG", new File(targetPath));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 图像切割（指定切片的行数和列数）
     * @param descDir 切片目标文件夹
     * @param rows 目标切片行数。默认2，必须是范围 [1, 20] 之内
     * @param cols 目标切片列数。默认2，必须是范围 [1, 20] 之内
     */
    public final void cutByFile2(String descDir, int rows, int cols) {
        try {
            if(rows<=0||rows>20){
                rows = 2; // 切片行数
            }
            if(cols<=0||cols>20){
                cols = 2; // 切片列数
            }
            // 读取源图像
            BufferedImage bi = ImageIO.read(new File(sourceTemplate));
            int srcWidth = bi.getHeight(); // 源图宽度
            int srcHeight = bi.getWidth(); // 源图高度
            if (srcWidth > 0 && srcHeight > 0) {
                Image img;
                ImageFilter cropFilter;
                Image image = bi.getScaledInstance(srcWidth, srcHeight, Image.SCALE_DEFAULT);
                int destWidth = srcWidth; // 每张切片的宽度
                int destHeight = srcHeight; // 每张切片的高度
                // 计算切片的宽度和高度
                if (srcWidth % cols == 0) {
                    destWidth = srcWidth / cols;
                } else {
                    destWidth = (int) Math.floor(srcWidth / cols) + 1;
                }
                if (srcHeight % rows == 0) {
                    destHeight = srcHeight / rows;
                } else {
                    destHeight = (int) Math.floor(srcWidth / rows) + 1;
                }
                // 循环建立切片
                // 改进的想法:是否可用多线程加快切割速度
                for (int i = 0; i < rows; i++) {
                    for (int j = 0; j < cols; j++) {
                        // 四个参数分别为图像起点坐标和宽高
                        // 即: CropImageFilter(int x,int y,int width,int height)
                        cropFilter = new CropImageFilter(j * destWidth, i * destHeight,
                                destWidth, destHeight);
                        img = Toolkit.getDefaultToolkit().createImage(
                                new FilteredImageSource(image.getSource(),
                                        cropFilter));
                        BufferedImage tag = new BufferedImage(destWidth,
                                destHeight, BufferedImage.TYPE_INT_RGB);
                        Graphics g = tag.getGraphics();
                        g.drawImage(img, 0, 0, null); // 绘制缩小后的图
                        g.dispose();
                        // 输出为文件
                        ImageIO.write(tag, "JPEG", new File(descDir
                                + "_r" + i + "_c" + j + ".jpg"));
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * 图像切割（指定切片的宽度和高度）
     * @param descDir 切片目标文件夹
     * @param destWidth 目标切片宽度。默认200
     * @param destHeight 目标切片高度。默认150
     */
    public final void cutByFile3(String descDir, int destWidth, int destHeight) {
        try {
            if(destWidth<=0){
                destWidth = 200; // 切片宽度
            }
            if(destHeight<=0){
                destHeight = 150; // 切片高度
            }
            // 读取源图像
            BufferedImage bi = ImageIO.read(new File(sourceTemplate));
            int srcWidth = bi.getHeight(); // 源图宽度
            int srcHeight = bi.getWidth(); // 源图高度
            if (srcWidth > destWidth && srcHeight > destHeight) {
                Image img;
                ImageFilter cropFilter;
                Image image = bi.getScaledInstance(srcWidth, srcHeight, Image.SCALE_DEFAULT);
                int cols = 0; // 切片横向数量
                int rows = 0; // 切片纵向数量
                // 计算切片的横向和纵向数量
                if (srcWidth % destWidth == 0) {
                    cols = srcWidth / destWidth;
                } else {
                    cols = (int) Math.floor(srcWidth / destWidth) + 1;
                }
                if (srcHeight % destHeight == 0) {
                    rows = srcHeight / destHeight;
                } else {
                    rows = (int) Math.floor(srcHeight / destHeight) + 1;
                }
                // 循环建立切片
                // 改进的想法:是否可用多线程加快切割速度
                for (int i = 0; i < rows; i++) {
                    for (int j = 0; j < cols; j++) {
                        // 四个参数分别为图像起点坐标和宽高
                        // 即: CropImageFilter(int x,int y,int width,int height)
                        cropFilter = new CropImageFilter(j * destWidth, i * destHeight,
                                destWidth, destHeight);
                        img = Toolkit.getDefaultToolkit().createImage(
                                new FilteredImageSource(image.getSource(),
                                        cropFilter));
                        BufferedImage tag = new BufferedImage(destWidth,
                                destHeight, BufferedImage.TYPE_INT_RGB);
                        Graphics g = tag.getGraphics();
                        g.drawImage(img, 0, 0, null); // 绘制缩小后的图
                        g.dispose();
                        // 输出为文件
                        ImageIO.write(tag, "JPEG", new File(descDir
                                + "_r" + i + "_c" + j + ".jpg"));
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /************************ 图像类型转换 ***************************/
    /**
     * 图像类型转换：GIF->JPG、GIF->PNG、PNG->JPG、PNG->GIF(X)、BMP->PNG
     * @param formatName 包含格式非正式名称的 String：如JPG、JPEG、GIF等
     */
    public final void convertByFile(String formatName) {
        try {
            File f = new File(sourceTemplate);
            f.canRead();
            f.canWrite();
            BufferedImage src = ImageIO.read(f);
            ImageIO.write(src, formatName, new File(targetPath));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /************************ 彩色转为黑白 ***************************/
    public final void grayByFile() {
        try {
            BufferedImage src = ImageIO.read(new File(sourceTemplate));
            ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
            ColorConvertOp op = new ColorConvertOp(cs, null);
            src = op.filter(src, null);
            ImageIO.write(src, "JPEG", new File(targetPath));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /************************ 获取图片宽高 ***************************/
    public final Map<String, Integer> getStyleByFile(){
        Map<String, Integer> map = new HashMap<>(2);
        try {
            File img = new File(sourceTemplate);
            Image src = ImageIO.read(img);
            map.put("height", src.getHeight(null));
            map.put("width", src.getWidth(null));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return map;
    }
}
